﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Media;
using ChromaCode.Extensions;
using System.IO;
using System.Windows.Documents;
using System.Windows;

namespace ChromaCode.Services
{
    /// <summary>
    /// Apply a set of rules to a document
    /// </summary>
    internal class DocumentService
    {
        #region Public

        /// <summary>
        /// Default constructor
        /// </summary>
        public DocumentService()
        {
            m_Rules = new List<Models.Rule>();
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="ruleXml">The path to an xml file that contains the highlighting rules to use</param>
        public DocumentService(string ruleXml)
            : this()
        {
            LoadRules(ruleXml);
        }

        /// <summary>
        /// Load a rule xml file
        /// </summary>
        /// <param name="ruleXml">The path to an xml file that contains highlighting rules</param>
        /// <returns>True if the rules were loaded successfully</returns>
        /// <exception cref="FileNotFoundException">If file not found</exception>
        public void LoadRules(string ruleXml)
        {
            // Does the file exist
            if (File.Exists(ruleXml))
            {
                // Load rule file
                XDocument xDoc = XDocument.Load(ruleXml);

                // Clear out any old rules
                m_Rules.Clear();

                // Load rule data
                LoadWordRules(xDoc);
                LoadCharacterRules(xDoc);
                LoadGroupRules(xDoc);
                LoadRegExprRules(xDoc);
            }
            else
            {
                // File does not exist so throw an exception
                throw new FileNotFoundException(string.Format("Cannot find or load \"{0}\"", ruleXml));
            }
        }

        /// <summary>
        /// Apply hightlighting to supplied text range from a rich text box
        /// </summary>
        /// <param name="paragraphToHighlight">The paragraph highlight</param>
        /// <param name="highlightWholeDoc">Thw whole document is having its highligting refreshed so some highlight
        /// processes need to be aware of this</param>
        public void HighlightRange(Paragraph paragraphToHighlight, bool highlightWholeDoc)
        {
            List<Models.HighlightElement> elements = new List<Models.HighlightElement>();

            // Get character highlight elements
            CharRuleService charService = new CharRuleService(m_Rules, paragraphToHighlight);
            elements.AddRange(charService.HighlightElements);

            // Get word highlight elements
            WordRuleService wordService = new WordRuleService(m_Rules, paragraphToHighlight);
            elements.AddRange(wordService.HighlightElements);

            // Get regular expression highlight elements
            RegExRuleService regExpService = new RegExRuleService(m_Rules, paragraphToHighlight);
            elements.AddRange(regExpService.HighlightElements);

            // Get group highlight elements
            GroupRuleService groupService = new GroupRuleService(m_Rules, paragraphToHighlight, highlightWholeDoc);
            elements.AddRange(groupService.HighlightElements);

            // Now apply the highlighting
            foreach (Models.HighlightElement element in elements)
            {
                element.ApplyRule();
            }
        }

        #endregion Public

        #region Private

        /// <summary>
        /// Load Word Rules from the supplied XDocument
        /// </summary>
        /// <param name="xDoc">The xml file containing the word rules</param>
        private void LoadWordRules(XDocument xDoc)
        {
            // Get a list of word rules
            IEnumerable<XElement> xWordRules = xDoc.Descendants("wordRules");

            // If we got any rules, load and store them
            if (xWordRules != null)
            {
                List<Models.WordRule> wordRules = (from rule in xWordRules.Descendants("rule")
                                                   select new Models.WordRule
                                                   {
                                                       Active = rule.GetAttributeBoolValue("active", true),
                                                       Bold = rule.GetAttributeBoolValue("bold", false),
                                                       Italic = rule.GetAttributeBoolValue("italic", false),
                                                       Underline = rule.GetAttributeBoolValue("underline", false),
                                                       TypeOfRule = Models.RuleType.WordHighlighter,
                                                       Color = rule.GetAttributeBrushValue("color", Brushes.Black),
                                                       Words = rule.GetElementValueStringList("words")
                                                   }).ToList();

                // Add the word rules to the loaded rules list
                m_Rules.AddRange(wordRules);
            }
        }

        /// <summary>
        /// Load Character Rules from the supplied XDocument
        /// </summary>
        /// <param name="xDoc">The xml file containing the character rules</param>
        private void LoadCharacterRules(XDocument xDoc)
        {
            // Get a list of character rules
            IEnumerable<XElement> xCharRules = xDoc.Descendants("characterRules");

            // IF we got any rules then load and store them
            if (xCharRules != null)
            {
                List<Models.CharacterRule> charRules = (from rule in xCharRules.Descendants("rule")
                                                        select new Models.CharacterRule
                                                        {
                                                            Active = rule.GetAttributeBoolValue("active", true),
                                                            Bold = rule.GetAttributeBoolValue("bold", false),
                                                            Italic = rule.GetAttributeBoolValue("italic", false),
                                                            Underline = rule.GetAttributeBoolValue("underline", false),
                                                            TypeOfRule = Models.RuleType.CharacterHighlighter,
                                                            Color = rule.GetAttributeBrushValue("color", Brushes.Black),
                                                            Characters = rule.GetElementValueCharacterList("characters")
                                                        }).ToList();

                m_Rules.AddRange(charRules);
            }
        }

        /// <summary>
        /// Load Group Rules from the supplied XDocument
        /// </summary>
        /// <param name="xDoc">The xml file containing the group rules</param>
        private void LoadGroupRules(XDocument xDoc)
        {
            // Get a list of group rules
            IEnumerable<XElement> xGroupRules = xDoc.Descendants("groupRules");

            // IF we got any rules then load and store them
            if (xGroupRules != null)
            {
                List<Models.GroupRule> groupRules = (from rule in xGroupRules.Descendants("rule")
                                                     select new Models.GroupRule
                                                     {
                                                         Active = rule.GetAttributeBoolValue("active", true),
                                                         Bold = rule.GetAttributeBoolValue("bold", false),
                                                         Italic = rule.GetAttributeBoolValue("italic", false),
                                                         Underline = rule.GetAttributeBoolValue("underline", false),
                                                         TypeOfRule = Models.RuleType.GroupHighlighter,
                                                         Color = rule.GetAttributeBrushValue("color", Brushes.Black),
                                                         StartString = rule.GetAttributeStringValue("startString", string.Empty),
                                                         EndString = rule.GetAttributeStringValue("endString", string.Empty)
                                                     }).ToList();

                m_Rules.AddRange(groupRules);
            }
        }

        /// <summary>
        /// Load Regular Expression Rules from the supplied XDocument
        /// </summary>
        /// <param name="xDoc">The xml file containing the reg exp rules</param>
        private void LoadRegExprRules(XDocument xDoc)
        {
            // Get a list of reg expr rules
            IEnumerable<XElement> xRegExpRules = xDoc.Descendants("regExpRules");

            // IF we got any rules then load and store them
            if (xRegExpRules != null)
            {
                List<Models.RegExpRule> regExpRules = (from rule in xRegExpRules.Descendants("rule")
                                                       select new Models.RegExpRule
                                                     {
                                                         Active = rule.GetAttributeBoolValue("active", true),
                                                         Bold = rule.GetAttributeBoolValue("bold", false),
                                                         Italic = rule.GetAttributeBoolValue("italic", false),
                                                         Underline = rule.GetAttributeBoolValue("underline", false),
                                                         TypeOfRule = Models.RuleType.RegExpHighlighter,
                                                         Color = rule.GetAttributeBrushValue("color", Brushes.Black),
                                                         RegExpString = rule.GetChildElementStringValue("regExp")
                                                     }).ToList();

                m_Rules.AddRange(regExpRules);
            }
        }

        /// <summary>
        /// A list of rules to apply to the document represented by this service
        /// </summary>
        private List<Models.Rule> m_Rules;

        #endregion Private
    }
}
